/*
 * jPOS Project [http://jpos.org]
 * Copyright (C) 2000-2015 Alejandro P. Revilla
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.jpos.transaction.participant;

import bsh.EvalError;
import org.jdom.Element;
import org.jpos.core.ConfigurationException;
import org.jpos.core.XmlConfigurable;
import org.jpos.transaction.AbortParticipant;
import org.jpos.transaction.TransactionParticipant;
import org.jpos.util.LogEvent;
import org.jpos.util.Logger;
import org.jpos.util.SimpleLogSource;

import java.io.IOException;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;

/**
 * A TransactionParticipant whose prepare, commit and abort methods can be
 * specified through beanshell scripts. <BR>
 * <p/>
 * To indicate what code to execute for any of the methods just add an element
 * named 'prepare', 'commit' or 'abort' contained in that of the participant. <BR>
 * <p/>
 * See BSHMethod for details on the syntax of these elements. The value to return
 * in the prepare method should be stored in the script variable named "result".
 * None of these tags are mandatory. <BR>
 * <p/>
 * You can subclass BSHTransactionParticipant and override the default...
 * methods. That way you can provide default behaviour for a participant and
 * override it at deploy time through scripts.
 *
 * @author AMarques
 * @see BSHMethod
 */
@SuppressWarnings("unchecked")
public class BSHTransactionParticipant extends SimpleLogSource
		implements TransactionParticipant, AbortParticipant, XmlConfigurable {

	protected BSHMethod prepareMethod;
	protected BSHMethod prepareForAbortMethod;
	protected BSHMethod commitMethod;
	protected BSHMethod abortMethod;

	boolean trace;

	/**
	 * Creates a new instance of BSHTransactionParticipant
	 */
	public BSHTransactionParticipant() {
		super();
	}

	public void abort(long id, java.io.Serializable context) {
		LogEvent ev = new LogEvent(this, "abort");
		if (abortMethod != null) {
			try {
				executeMethod(abortMethod, id, context, ev, "");
			} catch (Exception ex) {
				ev.addMessage(ex);
			}
		} else {
			defaultAbort(id, context, ev);
		}
		if (trace)
			Logger.log(ev);
	}

	protected void defaultAbort(long id, Serializable context, LogEvent ev) {
	}

	public void commit(long id, java.io.Serializable context) {
		LogEvent ev = new LogEvent(this, "commit");
		if (commitMethod != null) {
			try {
				executeMethod(commitMethod, id, context, ev, "");
			} catch (Exception ex) {
				ev.addMessage(ex);
			}
		} else {
			defaultCommit(id, context, ev);
		}
		if (trace)
			Logger.log(ev);
	}

	protected void defaultCommit(long id, Serializable context, LogEvent ev) {
	}

	public int prepare(long id, java.io.Serializable context) {
		LogEvent ev = new LogEvent(this, "prepare");
		int result = ABORTED | READONLY;
		if (prepareMethod != null) {
			try {
				result = (Integer) executeMethod(prepareMethod, id, context, ev, "result");
			} catch (Exception ex) {
				ev.addMessage(ex);
			}
		} else {
			result = defaultPrepare(id, context, ev);
		}
		ev.addMessage("result", Integer.toBinaryString(result));
		if (trace)
			Logger.log(ev);
		return result;
	}

	public int prepareForAbort(long id, java.io.Serializable context) {
		LogEvent ev = new LogEvent(this, "prepare-for-abort");
		int result = ABORTED | READONLY;
		if (prepareForAbortMethod != null) {
			try {
				result = (Integer) executeMethod(prepareForAbortMethod, id, context, ev, "result");
			} catch (Exception ex) {
				ev.addMessage(ex);
			}
		}
		ev.addMessage("result", Integer.toBinaryString(result));
		if (trace)
			Logger.log(ev);
		return result;
	}

	protected int defaultPrepare(long id, Serializable context, LogEvent ev) {
		return PREPARED | READONLY;
	}

	public void setConfiguration(Element e) throws ConfigurationException {
		try {
			prepareMethod = BSHMethod.createBshMethod(e.getChild("prepare"));
			prepareForAbortMethod = BSHMethod.createBshMethod(e.getChild("prepare-for-abort"));
			commitMethod = BSHMethod.createBshMethod(e.getChild("commit"));
			abortMethod = BSHMethod.createBshMethod(e.getChild("abort"));
			trace = "yes".equals(e.getAttributeValue("trace"));
		} catch (Exception ex) {
			throw new ConfigurationException(ex.getMessage(), ex);
		}
	}

	protected Object executeMethod(BSHMethod m, long id, Serializable context, LogEvent evt, String resultName)
			throws EvalError, IOException {
		Map params = new HashMap();
		params.put("context", context);
		params.put("id", id);
		params.put("evt", evt);
		params.put("self", this);
		return m.execute(params, resultName);
	}
}

